#include "StdAfx.h"
#include "AutoJoin.h"

#define OS_CAVELENGTH 0x32	// Offset_ Cave Code Length

//CBalloon* CAutoJoin::balloon = NULL;
HWND CAutoJoin::hWndStartGame = 0;

CAutoJoin::CAutoJoin(CDialog *dlg, ISetting* setting, ILang *lang, CBalloon* balloon)
{
	this->dlg = dlg;
	this->setting = setting;
	this->lang = lang;
	this->balloon = balloon;

	bJoinNext = FALSE;
	bPending = FALSE;

	poJoin.y = -1;
	poBegin.y = -1;
	poEnd.y = -1;

	dlgLine = NULL;

	TCHAR szAppData[MAX_PATH];
	SHGetSpecialFolderPath(NULL, szAppData, CSIDL_APPDATA, 0);
	wcscat_s(szAppData, L"\\Auto-Joiner\\");
	CreateDirectory(szAppData, NULL);
	sfPatchGG = sfRestoreGG = sfPatchGM = sfRestoreGM = szAppData;
	sfPatchGG += L"patchgg.dat";
	sfRestoreGG += L"restoregg.dat";
	sfPatchGM += L"patchgm.dat";
	sfRestoreGM += L"restoregm.dat";
}

CAutoJoin::~CAutoJoin(void)
{
}


LRESULT CAutoJoin::OnHotKey(WPARAM wp) {
	switch(wp) {
		case CAutoJoinDlg::HK_F7:
			OnF7();
			break;

		case CAutoJoinDlg::HK_F8:
			::MessageBox(NULL, L"f8", L"OK", MB_OK);
			break;

		default:
			::MessageBox(NULL, L"fx", L"OK", MB_OK);
			break;
	}

	return 0;
}

void CAutoJoin::OnF7() {
	if(poBegin.y == -1) {
		// Press the first F7
		GetCursorPos(&poBegin);
		if(IsOnARoom(&poBegin)) {
			hWndRoom = CWnd::WindowFromPoint(poBegin);			
			hWndRoom->ScreenToClient(&poBegin);
			u::PostClick(hWndRoom, poBegin);

			dlgLine = new CDialog;
			dlgLine->Create(IDD_HTML, hWndRoom->GetParent());
			RECT rectRoom, rectParent;
			hWndRoom->GetParent()->GetWindowRect(&rectParent);
			hWndRoom->GetWindowRect(&rectRoom);		

			dlgLine->SetWindowPos(&CWnd::wndTop, rectRoom.left - rectParent.left, rectRoom.top - rectParent.top, 15, rectRoom.bottom - rectRoom.top, SWP_SHOWWINDOW  );

			CAutoJoinDlg::hookMouse = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC) CAutoJoinDlg::OnGlobalMouseAction, AfxGetInstanceHandle(), 0);
		}
	} else {
		// Press the second F7
		if(poEnd.y == -1) {
			UnhookWindowsHookEx(CAutoJoinDlg::hookMouse);
			CAutoJoinDlg::hookMouse = NULL;

			GetCursorPos(&poEnd);
			hWndRoom->ScreenToClient(&poEnd);
			RECT rectRoom;
			hWndRoom->GetWindowRect(&rectRoom);
			poEnd.y = min(max( 30, poEnd.y), rectRoom.bottom - rectRoom.top - 20);

			if(StartJoining()) {				
				balloon->Notify(lang->get(L"tostopF7", L"Auto-Joiner is running. To stop it: press F7."));
			} else {
				StopJoining();
			}
		} else {
			// Press the third F7
			balloon->Notify(L"");
			StopJoining();
		}
	}
}

void CAutoJoin::OnGlobalMouseAction(LPARAM lParam) 
{
	PMSLLHOOKSTRUCT  hookStruct = (PMSLLHOOKSTRUCT )(lParam);

	POINT po1 = poBegin;	
	POINT po2 = hookStruct->pt;
	hWndRoom->ScreenToClient(&po2);

	RECT rectRoom;
	hWndRoom->GetWindowRect(&rectRoom);
	po2.y = min(max(30, po2.y), rectRoom.bottom - rectRoom.top - 20);

	InvalidateRect(dlgLine->m_hWnd, NULL, TRUE);
	UpdateWindow(dlgLine->m_hWnd);

	POINT poTemp[3];
	CDC* hDC = dlgLine->GetDC();

	HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
	hDC->SelectObject(hBrush);

	HPEN hPen;
	// Line Down
	hPen = CreatePen(PS_SOLID, 4, RGB(255, 0, 0));
	hDC->SelectObject(hPen);

	hDC->Ellipse(3, po1.y - 3, 9, po1.y + 3);	

	hDC->MoveTo(6, po1.y);				

	if(po1.y < po2.y) {
		hDC->LineTo(6, po2.y - 4);
	} else {
		hDC->LineTo(6, po2.y + 4);
	}

	DeleteObject(hPen);

	//  arrow
	hPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	hDC->SelectObject(hPen);

	poTemp[0].x = 1;
	poTemp[1].x = 11;
	poTemp[2].x = 6;

	if(po1.y < po2.y) {
		poTemp[0].y = po2.y - 10;
		poTemp[1].y = po2.y - 10;	
		poTemp[2].y = po2.y;
	} else {

		poTemp[0].y = po2.y + 10;
		poTemp[1].y = po2.y + 10;
		poTemp[2].y = po2.y;
	}
	hDC->Polygon(poTemp, 3);

	DeleteObject(hPen);
	// 2 lines at 2 ends,
	hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
	hDC->SelectObject(hPen);

	hDC->MoveTo(5, po1.y);
	hDC->LineTo(15, po1.y);

	hDC->MoveTo(5, po2.y);
	hDC->LineTo(15, po2.y);

	DeleteObject(hBrush);
	DeleteObject(hPen);
	hWndRoom->ReleaseDC(hDC);	
}

bool CAutoJoin::StartJoining() {	
	// Get Garena Process Handle
	DWORD pid; 
	GetWindowThreadProcessId(hWndRoom->GetSafeHwnd(), &pid);
	pGarena = CProcess::FindProcess(pid);

	if(pGarena == NULL) {
		balloon->Notify(L"Could not open Garena process");			
		return false;		
	}

	bGG = false;
	if(_wcsicmp(pGarena->szProcessName, L"garena_room.exe")) {
		bGG = true;

		// Test whether Garena is patched
		if(!IsGGPatched()) {
			// think about it; 
			//COptionsDlg::txtGGPath->SetWindowText(pGarena->szExePath);
			balloon->Error(lang->get(L"notpatched", L"Please close Garena and use the [Start Garena] button."));
			return false;
		}
	}

	bSafe = setting->GetInt(L"safe", 0);

	if(!bSafe && !PatchGarenaMemory()) {
		balloon->Error(lang->get(L"patcherror", L"Could not patch Garena. Please contact autogarena@gmail.com"));
		return false;
	}

	if(poBegin.y > poEnd.y) {		
		swap(poBegin.y, poEnd.y);		
	}

	poJoin = poBegin;

	u::PostClick(hWndRoom, poJoin);		
	SendJoin(poJoin);

	HWND hWndGarena = u::GetParentWindow(hWndRoom->GetSafeHwnd(), -1);
	EnumChildWindows(hWndGarena, (WNDENUMPROC)EnumStartGameButton, NULL);	

	dlg->SetTimer(CAutoJoinDlg::TIMER_CHECK, 300, NULL);
	if(bSafe) {
		dlg->SetTimer(CAutoJoinDlg::TIMER_JOIN, 5500, NULL);
	} else {
		dlg->SetTimer(CAutoJoinDlg::TIMER_TIMEOUT, 20000, NULL);
	}

	//hDlg->SetDlgItemText(BTN_EXIT, lang->get(L"stop", L"Stop Joining"));	
	return true;
}

void CAutoJoin::StopJoining() {
	if(CAutoJoinDlg::hookMouse != NULL) {
		UnhookWindowsHookEx(CAutoJoinDlg::hookMouse);
		CAutoJoinDlg::hookMouse = NULL;
	}

	dlg->KillTimer(CAutoJoinDlg::TIMER_CHECK);
	if(bSafe) {
		dlg->KillTimer(CAutoJoinDlg::TIMER_JOIN);
		EnumChildWindows(0, (WNDENUMPROC)EnumChildProc, NULL);		
	} else {
		dlg->KillTimer(CAutoJoinDlg::TIMER_TIMEOUT);
		if(poEnd.y != -1) {		
			RestoreGarenaMemory();
		}
	}	

	hWndRoom = 0;

	POINT po = {-1, -1};
	poBegin = po;
	poEnd = po;
	poJoin = po;

	if(dlgLine != NULL) {
		dlgLine->DestroyWindow();
		dlgLine = NULL;
	}

	//CLang langMain(L"main");
	//SetDlgItemText(hDlg, BTN_EXIT, langMain.get(L"exit", L""));
}

BOOL CALLBACK CAutoJoin::EnumChildProc(HWND hwnd, LPARAM lparam) {
	// Room reached limit dialog
	LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
	switch(lStyle) {
	case 0x9408004C:
		SendMessage(hwnd, WM_COMMAND, 0x80E, 0);
		break;

	case 0x9400004C:
		SendMessage(hwnd, WM_COMMAND, 0x7FD, 0);
		break;

		// Garena Messenger error dialog:
	case 0x94C8004C:
		SendMessage(hwnd, WM_COMMAND, 0x1, 0);
		break;
	}

	return true;
}

BOOL CALLBACK CAutoJoin::EnumStartGameButton(HWND hwnd, LPARAM lparam) {	

	int id = GetDlgCtrlID(hwnd);	
	// 0x488 is control ID of Start-Game button
	if(id == 0x488) {				
		hWndStartGame = hwnd;
	}

	return true;
}

void CAutoJoin::OnTimer(WPARAM wParam) {

	switch(wParam) {
	case CAutoJoinDlg::TIMER_CHECK:		
		if(IsWindowVisible(hWndStartGame) == TRUE) {
			StopJoining();

			// Play sound
			if(setting->GetInt(L"sound", 1)) {
				PlaySound(L"Data\\tada.wav", NULL, SND_FILENAME | SND_ASYNC);
			}

			if(setting->GetInt(L"autotunnel", 0)) {
				dlg->SetTimer(CAutoJoinDlg::TIMER_TUNNEL, 1000, NULL);
			} else {
				// Close AJ					
				if(setting->GetInt(L"closeaj", 0)) {
					//CAutoJoin::PrepareCloseAJ(hDlg);
				}
			}

			balloon->Notify(lang->get(L"joined", L""));

		} else {
			bJoinNext = TRUE;			
			if(bPending == TRUE) {
				bPending = FALSE;				
				JoinNextRoom();
			}

		}
		break;

	case CAutoJoinDlg::TIMER_TIMEOUT:
		dlg->KillTimer(CAutoJoinDlg::TIMER_TIMEOUT);
		JoinNextRoom();		
		break;

	case CAutoJoinDlg::TIMER_JOIN:
		EnumChildWindows(0, (WNDENUMPROC)EnumChildProc, NULL);
		JoinNextRoom();
		break;

	case CAutoJoinDlg::TIMER_TUNNEL:
		dlg->KillTimer(CAutoJoinDlg::TIMER_TUNNEL);
		//StartTunnel();
		break;
	}


}

void CAutoJoin::SendJoin(POINT po) {
	//if(bAT) {	// auto-trace		
	//	PostMessage(hWndRoom, WM_COMMAND, WP_TRACE, 0);
	//} else {	// auto-join
		hWndRoom->PostMessage(WM_LBUTTONDBLCLK, 0, MAKELPARAM(po.x, po.y));				
	//}
}

void CAutoJoin::JoinNextRoom() {	
	// These variables help Auto-Joiner no longer join faster than 300ms/join.
	if(!bSafe && bJoinNext == FALSE) {
		bPending = TRUE;
		return;
	}

	if(hWndRoom->IsWindowVisible() == TRUE) {
		if(GetRoomLineNo(poJoin) < GetRoomLineNo(poEnd)) {
			poJoin.y += 25;
		} else {
			poJoin.y = poBegin.y;
		}

		bJoinNext = FALSE;
		if(bSafe) {
		} else {
			dlg->SetTimer(CAutoJoinDlg::TIMER_TIMEOUT, 20000, NULL);
		}
		u::PostClick(hWndRoom, poJoin);
		SendJoin(poJoin);
	}

}


bool CAutoJoin::PatchGarenaMemory() {			
	//DWORD dwCaveStart = pGarena->dwDataAddress - OS_CAVELENGTH;	

	if(bGG) { 		
		// If Garena Classic
		return (DetectGGMemory() && pGarena->Write(sfPatchGG));
	} else {
		// If Garena Plus
		//mi(DetectGMMemory()); mi(pGarena->Write(sfPatchGM, FALSE));return false;
		return (DetectGMMemory() && pGarena->Write(sfPatchGM));
	}
}

BOOL CAutoJoin::RestoreGarenaMemory() {
	if(bGG) {
		return pGarena->Write(sfRestoreGG);
	} else {
		return pGarena->Write(sfRestoreGM);
	}
}

bool CAutoJoin::InsertSendMessage(ofstream& fp, ofstream& fr, DWORD dwCave) {
	char saTemp[256];
	DWORD dwTemp;

	HMODULE hUSER32 = pGarena->GetModuleInfo(L"USER32.dll").hModule;
	if(!hUSER32) return false;

	// Push 0, Push 0, Push 8924, Push
	BYTE bSendMsg[10] = {0x6A, 0x00, 0x6A, 0x00, 0x68, 0x24, 0x89, 0x00, 0x00, 0x68};	
	u::ByteArrayToString((BYTE*)&bSendMsg, saTemp, 10);
	fp << std::hex << dwCave << " " << saTemp;
	dwCave += 10;

	// Push hDlg
	HWND hDlg = dlg->GetSafeHwnd();
	u::ByteArrayToString((BYTE*)&hDlg, saTemp, 4);
	fp << std::hex << saTemp << "\n";
	dwCave += 4;

	// Insert PostMessageW
	FARPROC fnSendMessage = GetProcAddress(hUSER32, "PostMessageA");
	dwTemp = *(DWORD*)&fnSendMessage - (dwCave + 5);
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwCave << " E8" << saTemp << "\n";

	return true;
}

bool CAutoJoin::DetectGGMemory() {	
	ofstream fp(sfPatchGG);
	ofstream fr(sfRestoreGG);

	DWORD dwTemp;
	BYTE bTemp[6];
	char saTemp[256];

	//-- PATCH 5 SECONDS --
	BYTE b5s[3] = {0x73, 0x60, 0x68};
	DWORD dwGG5s = pGarena->dwCodeAddress;
	if(pGarena->Find(&dwGG5s, b5s, 3)) {
		fp << std::hex << dwGG5s << " EB\n";
		fr << std::hex << dwGG5s << " 73\n";
	}	

	//-- CHANGE ShowWindow to JMP TO CAVE CODE--
	// Find ShowWindow address	
	BYTE bShowWindow[4] = {0x3D, 0x00, 0xFF, 0x15};	
	DWORD dwGGShowWindow = pGarena->dwCodeAddress;
	if(!pGarena->Find(&dwGGShowWindow, bShowWindow, 4)) {
		BYTE bShowWindowPatched[3] = {0x3D, 0x00, 0xE9};	
		dwGGShowWindow = pGarena->dwCodeAddress;
		if(!pGarena->Find(&dwGGShowWindow, bShowWindowPatched, 3)) {				
			return false;			
		}
	}
	dwGGShowWindow += 2;	

	pGarena->Read(dwGGShowWindow, bTemp, 6);
	u::ByteArrayToString(bTemp, saTemp, 6);
	fr << std::hex << dwGGShowWindow << " " << saTemp << "\n";

	DWORD dwCaveStart = pGarena->dwDataAddress - OS_CAVELENGTH;

	// JMP CAVE_CODE; NOP
	dwTemp = dwCaveStart - (dwGGShowWindow + 5);
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwGGShowWindow << " E9" << saTemp << "90\n";	


	//--- CAVE CODE ----

	// Get the PrevFunc relative address
	HMODULE hUSER32 = pGarena->GetModuleInfo(L"USER32.dll").hModule;
	if(!hUSER32) return false;

	FARPROC fnShowWindow = GetProcAddress(hUSER32, "ShowWindow");	
	DWORD dwPrevFunc = *(DWORD*)&fnShowWindow - (dwCaveStart + 5);
	u::ByteArrayToString((BYTE*)&dwPrevFunc, saTemp, 4);
	fp << std::hex << dwCaveStart << " E8" << saTemp << "\n";	

	// Insert PostMessage
	InsertSendMessage(fp, fr, dwCaveStart + 5);

	//-- Jump Back From End of Cave Code To Show Window --
	// JMP xxxxx
	dwTemp = (dwGGShowWindow + 6) - (dwCaveStart + 0x1D);	
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwCaveStart + 0x18 << " E9" << saTemp << "\n";	


	//-- PATCH ALL DialogBox --
	BYTE bDlg[4] = {0x14, 0x00, 0x75, 0x0C};	
	for(dwTemp = pGarena->dwCodeAddress; dwTemp < pGarena->dwDataAddress; dwTemp++) {
		if(pGarena->Find(&dwTemp, bDlg, 4)) {
			pGarena->Read(dwTemp + 0x68, bTemp, 1);
			if(bTemp[0] == 0xFF) {				
				fp << std::hex << dwTemp - 4 << " B80100000090EB6A\n";				
				fr << std::hex << dwTemp - 4 << " 8BF1837E1400750C\n";
			}
		}
	}

	fp.close();
	fr.close();
	return true;
}


bool CAutoJoin::DetectGMMemory() {
	ofstream fp(sfPatchGM);
	ofstream fr(sfRestoreGM);

	DWORD dwTemp;
	char saTemp[128];

	//-- PATCH 5 SECONDS --//
	//BYTE b5s[4] = {0x0F, 0x83, 0x95, 0x00};
	BYTE b5s[6] = {0x3D, 0x88, 0x13, 0x00, 0x00, 0x0F};
	DWORD dwGM5s = pGarena->dwCodeAddress;
	if(!pGarena->Find(&dwGM5s, b5s, 6)) return false;
	dwGM5s += 5;
	//mh(dwGM5s);
	fp << std::hex << dwGM5s << " E99600000090\n";
	fr << std::hex << dwGM5s << " 0F8395000000\n";				

	//-- CREATE PATH WAY TO CAVE CODE --//	
	BYTE bShowWindow[6] = {0xC6, 0x40, 0x51, 0x00, 0x6A, 0x00};	
	DWORD dwGMShowWindow = pGarena->dwCodeAddress;
	if(!pGarena->Find(&dwGMShowWindow, bShowWindow, 4)) return false;
	dwGMShowWindow += 0xC;

	// Backup for Restore later
	BYTE bBackupShowWindow[6];
	pGarena->Read(dwGMShowWindow, bBackupShowWindow, 5);
	u::ByteArrayToString(bBackupShowWindow, saTemp, 5);
	fr << std::hex << dwGMShowWindow << " " << saTemp << "\n";			

	DWORD dwCaveStart = pGarena->dwDataAddress - OS_CAVELENGTH;	

	dwTemp = dwCaveStart - (dwGMShowWindow + 5);
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwGMShowWindow << " E9" << saTemp << "\n";	

	//-- Generate CALL PrevFunc code --//		
	dwTemp = (dwGMShowWindow + 5) + *(DWORD*)(bBackupShowWindow + 1) - (dwCaveStart + 5);	
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwCaveStart << " E8" << saTemp << "\n";				

	// Insert PostMessage
	InsertSendMessage(fp, fr, dwCaveStart + 5);

	//-- Jump Back To Show Window --
	// JMP xxxxx
	dwTemp = (dwGMShowWindow + 5) - (dwCaveStart + 0x1D);	
	u::ByteArrayToString((BYTE*)&dwTemp, saTemp, 4);
	fp << std::hex << dwCaveStart + 0x18 << " E9" << saTemp << "\n";


	//-- PATCH ALL DialogBox --//
	BYTE bDlg[6] = {0x83, 0x7D, 0xFC, 0x00, 0x75, 0x0D};
	BYTE bMovEax[6] = {0xB8, 0x01, 0x00, 0x00, 0x00, 0x90}; // MOV EAX, 1; NOP;
	for(dwTemp = pGarena->dwCodeAddress; dwTemp < pGarena->dwDataAddress; dwTemp++) {
		if(pGarena->Find(&dwTemp, bDlg, 6)) {	
			fp << std::hex << dwTemp << " B80100000090EB43\n";
			fr << std::hex << dwTemp << " 837DFC00750D6A0E\n";
		}
	}	

	fp.close();
	fr.close();
	return true;
}


bool CAutoJoin::IsJoining() {
	return poEnd.y != -1;
}

int CAutoJoin::GetRoomLineNo(POINT po) {
	if(bGG) {
		return (po.y - 21) / 25;
	} else {
		return (po.y - 29) / 25;
	}
}

BOOL CAutoJoin::IsOnARoom(POINT* po) {
	// This function specifies whether the pointer is above Room List or Friend List or Other Windows
	// (of both Garena Classic and Garena Plus)	

	HWND hWnd = WindowFromPoint(*po);	
	TCHAR szClassName[256];
	GetClassName(hWnd, szClassName, 256);
		
	if(wcscmp(szClassName, L"GG_LISTVIEW") && wcscmp(szClassName, L"WTL_LISTVIEW")) {
		po->y = -1;
		balloon->Notify(L"hichic");
		//lang->get(L"clickroom", L"Please click on a room.")
		return false;
	}

	return true;
}

bool CAutoJoin::IsGGPatched() {
	if(!bGG) {
		return true;
	}
	// Test whether Garena Classic is patched
	BYTE bufPatched[6] = {0xE9, 0xB7, 0x00, 0x00, 0x00, 0x90};
	BYTE bufNotPatched[6] = {0x0F, 0x85, 0xFF, 0x09, 0x00, 0x00};

	static DWORD dwPatch = 0;
	if(dwPatch == 0) {
		dwPatch = pGarena->dwCodeAddress;
		if(!pGarena->Find(&dwPatch, bufPatched, 6)) {
			
			dwPatch = pGarena->dwCodeAddress;
			pGarena->Find(&dwPatch, bufNotPatched, 6);
		}
	}

	if(!pGarena->Verify(dwPatch, bufPatched, 6)) {
		if(!pGarena->Verify(dwPatch, bufNotPatched, 6)) {
			dwPatch = 0;
		}		
		return false;
	}
	return true;
}